package org.classdef.analyzer;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import org.classdef.analyzer.info.ClassDef;
import org.classdef.analyzer.info.attribute.AttributePool;
import org.classdef.analyzer.info.constant.ConstantPool;
import org.classdef.analyzer.info.field.FieldPool;
import org.classdef.analyzer.info.method.MethodPool;

/**
 * 
 * 方法的访问类型
 *
 */
public class ClassAnalyzer {
	
	public ClassDef analyze(File classFile) throws FileNotFoundException {
		InputStream is = new FileInputStream(classFile);
		return analyze(is);
	}
	
	public ClassDef analyze(InputStream is) throws FileNotFoundException {
		DataInputStream input = new DataInputStream(new BufferedInputStream(is));
		try {
			ClassDef classDef = new ClassDef();
			
			//1. 读取魔数
			int magic = u4(input);
			classDef.setMagic(magic);
			if (magic != 0xCAFEBABE) {
				throw new RuntimeException("不是class格式的文件！");
			}
				
			//2. 读取副、主版本号
			short minor = u2(input);
			short major = u2(input);
			classDef.setMinor(minor);
			classDef.setMajor(major);
			
			
			
			//3. 读取池表中表项的个数
			short constantPoolCnt = u2(input);
			classDef.setConstantPoolCount(constantPoolCnt);
			
			//4. 读取常量池
			ConstantPool constantPool = analyzeConstantPool(input, constantPoolCnt);
			classDef.setConstantPool(constantPool);
			
			//5. 读取Class的声明中使用的修饰符掩码并解析
			short accessFlags = u2(input);
			classDef.setAccessFlags(accessFlags);
			
			//6. 读取类或者接口的全限定名称
			short thisClass = u2(input);
			classDef.setThisClass(thisClass);
			short superClass = u2(input);
			classDef.setSuperClass(superClass);
			
			//7. 读取接口个数:
			short interfacesCount = u2(input);
			classDef.setInterfacesCount(interfacesCount);
			
			//8. 读取接口名:
			for (int i = 0; i < interfacesCount; i++) {
				short interfaceIndex = u2(input);
				classDef.addInterfaceIndex(interfaceIndex);
			}
			
			//9. 读取成员变量个数:
			short fieldCount = u2(input);
			classDef.setFieldCount(fieldCount);
			
			//10. 读取成员变量:
			FieldPool fieldPool = analyzeFieldPool(input, classDef, fieldCount);
			fieldPool.setDef(classDef);
			classDef.setFieldPool(fieldPool);
			
			//11. 读取方法个数:
			short methodCount = u2(input);
			classDef.setMethodCount(methodCount);
			
			//12. 读取方法:
			MethodPool methodPool = analyzeMethodPool(input, classDef, methodCount);
			methodPool.setDef(classDef);
			classDef.setMethodPool(methodPool);
			

			//13. 读取attribute个数:
			short attributeCount = u2(input);
			classDef.setAttributeCount(attributeCount);
			
			//14. 读取attribute:
			AttributePool attributePool = analyzeAttributePool(input, classDef, attributeCount);
			attributePool.setDef(classDef);
			classDef.setAttributePool(attributePool);
			
			return classDef;
		}catch(Exception e){
			throw new RuntimeException(e.getMessage());
		}finally {
			if(input != null){
				try {
					input.close();
				} catch (IOException e) {
				}
			}
		}
	}

	private MethodPool analyzeMethodPool(DataInputStream input, ClassDef classDef, short methodCount) throws IOException {
		MethodPool methodPool = new MethodPool();
		methodPool.setDef(classDef);
		for (int i = 0; i < methodCount; i++) {
			analyzeMethodPool(input, methodPool, i);
		}
		return methodPool;
	}
	
	private void analyzeMethodPool(DataInputStream input, MethodPool methodPool, int index) throws IOException {
		short accessFlags = u2(input);
		short nameIndex = u2(input);
		short descriptorIndex = u2(input);
		short attributesCount = u2(input);
		
		AttributePool attributePool = analyzeAttributePool(input, methodPool.getDef(), attributesCount);
		methodPool.add(accessFlags, nameIndex, descriptorIndex, attributesCount, attributePool);
	}

	private FieldPool analyzeFieldPool(DataInputStream input, ClassDef classDef, short fieldCount) throws IOException {
		FieldPool fieldPool = new FieldPool();
		fieldPool.setDef(classDef);
		for (int i = 0; i < fieldCount; i++) {
			analyzeFieldPool(input, fieldPool, i);
		}
		return fieldPool;
	}
	
	private void analyzeFieldPool(DataInputStream input, FieldPool fieldPool, int index) throws IOException{
		short accessFlags = u2(input);
		short nameIndex = u2(input);
		short descriptorIndex = u2(input);
		short attributesCount = u2(input);
		
		AttributePool attributePool = analyzeAttributePool(input, fieldPool.getDef(), attributesCount);
		
		fieldPool.add(accessFlags, nameIndex, descriptorIndex, attributesCount, attributePool);
	}

	private AttributePool analyzeAttributePool(DataInputStream input, ClassDef classDef, short count) throws IOException {
		AttributePool attributePool = new AttributePool();
		attributePool.setDef(classDef);
		for (int i = 0; i < count; i++) {
			analyzeAttributePool(input, attributePool, i);
		}
		return attributePool;
	}

	private void analyzeAttributePool(DataInputStream input, AttributePool attributePool, int index) throws IOException {
		short attributeNameIndex = u2(input);
		int attributeLength = u4(input);
		
		byte[] info = new byte[attributeLength];
		input.readFully(info);
		
		attributePool.add(attributeNameIndex, attributeLength, info);
	}

	private ConstantPool analyzeConstantPool(DataInputStream input, int cnt) throws IOException {
		ConstantPool info = new ConstantPool();
		for (int i = 1; i < cnt; i++) {
			analyzeConstantPool(input, info, i); // 分析
		}
		return info;
	}
	
	private void analyzeConstantPool(DataInputStream input, ConstantPool info, int index) throws IOException {
		byte n8;
		short n16;
		short typeIndex;
		int n32;
		long n64;
		float f;
		double d;
		byte[] buffer;
		
		// 读取数据类型标签
		byte tag = u1(input); 
		
		switch (tag) {
			case 1: // utf-8 string
				// Utf8存储格式
				n16 = u2(input);
				buffer = new byte[n16];
				input.readFully(buffer); // 数组读满才返回
				
				info.add(tag, n16, buffer);
				break;
			case 3: // integer
				n32 = u4(input);
				
				info.add(tag, n32);
				break;
			case 4: // float
				f = u4(input);// input.readFloat();
				
				info.add(tag, f);
				break;
			case 5: // long
				n64 = u8(input);
				info.add(tag, n64);
				break;
			case 6: // double
				d = u8(input);
				info.add(tag, d);
				break;
			case 7: // class or interface reference
				typeIndex = u2(input);
				
				info.add(tag, typeIndex);
				break;
			case 8: // string
				typeIndex = u2(input);
				
				info.add(tag, typeIndex);
				break;
			case 9: // field reference
				typeIndex = u2(input);
				n16 = u2(input);
				
				info.add(tag, typeIndex, n16);
				break;
			case 10: // method reference
				typeIndex = u2(input);
				n16 = u2(input);
				
				info.add(tag, typeIndex, n16);
				break;
			case 11: // interface method reference
				typeIndex = u2(input);
				n16 = u2(input);
				
				info.add(tag, typeIndex, n16);
				break;
			case 12: // name and type reference
				typeIndex = u2(input);
				n16 = u2(input);
				
				info.add(tag, typeIndex, n16);
				break;
			case 15: // MethodHandle
				n8 = u1(input);
				n16 = u2(input);
				
				info.add(tag, n8, n16);
				break;
			case 16: // MethodType
				n16 = u2(input);
				
				info.add(tag, n16);
				break;
			case 18: // InvokeDynamic
				typeIndex = u2(input);
				n16 = u2(input);
				
				info.add(tag, typeIndex, n16);
				break;
			default:
				throw new RuntimeException("Unknown constant tag "+tag+" in class file");
		}
	}

	private byte u1(DataInputStream input) throws IOException {
		return input.readByte();
	}

	private Short u2(DataInputStream input) throws IOException {
		return input.readShort();
	}

	private int u4(DataInputStream input) throws IOException {
		return input.readInt();
	}

	private long u8(DataInputStream input) throws IOException {
		return input.readLong();
	}

}
